package com.eva.admin.biz.system;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.eva.admin.dao.system.SystemJobMapper;
import com.eva.admin.biz.system.dto.CreateSystemJobDTO;
import com.eva.admin.biz.system.dto.UpdateSystemJobDTO;
import com.eva.admin.biz.system.dto.UpdateSystemJobStatusDTO;
import com.eva.framework.common.constants.ResponseStatus;
import com.eva.framework.common.exception.BusinessException;
import com.eva.framework.common.model.PageData;
import com.eva.framework.common.model.PageWrap;
import com.eva.framework.common.utils.AssertUtil;
import com.eva.framework.mp.utils.PageUtil;
import com.eva.job.SchedulerProxy;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.eva.admin.dao.system.dto.QuerySystemJobDTO;
import com.eva.dao.system.model.SystemJob;
import com.eva.admin.dao.system.vo.SystemJobVO;
import com.eva.service.system.SystemJobService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.List;

@Slf4j
@Service
public class SystemJobBiz {

    @Resource
    private SystemJobService systemJobService;

    @Resource
    private SystemJobMapper systemJobMapper;

    @Resource
    private SchedulerProxy schedulerProxy;

    /**
     * 创建
     *
     * @param dto 创建参数
     */
    public void create(CreateSystemJobDTO dto) {
        // 基本验证
        this.checkCreate(dto);
        // 验证处理器是否重复
        if (systemJobService.exists(new SystemJob().setHandler(dto.getHandler()))) {
            throw new BusinessException(ResponseStatus.DATA_EXISTS, "已存在任务处理器'" + dto.getHandler() + "'，请勿重复绑定");
        }
        // 执行创建
        SystemJob newJob = new SystemJob();
        BeanUtils.copyProperties(dto, newJob);
        // - 默认创建的任务为禁用状态
        newJob.setDisabled(Boolean.TRUE);
        systemJobService.create(newJob);
    }

    /**
     * 根据主键删除
     *
     * @param id 主键
     */
    @Transactional
    public void deleteById(Integer id) {
        AssertUtil.notEmpty(id, "主键不能为空");
        // 查询任务
        SystemJob job = systemJobService.findById(id);
        AssertUtil.notNull(job, ResponseStatus.DATA_EMPTY);
        // 删除任务
        systemJobService.deleteById(id);
        // 从调度中移除任务
        try {
            schedulerProxy.deleteJob(job.getHandler());
        } catch (Exception e) {
            log.error("无法从调度中移除任务", e);
        }
    }

    /**
     * 根据主键批量删除
     *
     * @param ids 主键集合
     */
    @Transactional
    public void deleteByIdInBatch(List<Integer> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return;
        }
        for (Integer id : ids) {
            this.deleteById(id);
        }
    }

    /**
     * 更新
     *
     * @param dto 修改参数
     */
    public void updateById(UpdateSystemJobDTO dto) {
        // 基本验证
        AssertUtil.notNull(dto.getId(), "ID不能为空");
        this.checkCreate(dto);
        // 验证任务是否存在
        SystemJob job = systemJobService.findById(dto.getId());
        AssertUtil.notNull(job, ResponseStatus.DATA_EMPTY);
        // 验证任务是否启用，启用状态不允许调整（避免修改了处理器，导致调度混乱）
        if (!job.getDisabled()) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED, "该任务为启用状态，不可修改！");
        }
        // 验证处理器是否重复
        if (systemJobService.exists(SystemJob.builder().id(dto.getId()).handler(dto.getHandler()).build())) {
            throw new BusinessException(ResponseStatus.DATA_EXISTS, "已存在任务处理器'" + dto.getHandler() + "'，请勿重复绑定");
        }
        // 执行修改
        SystemJob newJob = new SystemJob();
        BeanUtils.copyProperties(dto, newJob);
        systemJobService.updateById(newJob);
    }

    /**
     * 更新禁用状态
     *
     * @param dto 修改参数
     */
    @Transactional
    public void updateStatusById(UpdateSystemJobStatusDTO dto) {
        // 基本验证
        AssertUtil.notNull(dto.getId(), "ID不能为空");
        AssertUtil.notNull(dto.getDisabled(), "状态不能为空");
        // 验证任务是否存在
        SystemJob job = systemJobService.findById(dto.getId());
        AssertUtil.notNull(job, ResponseStatus.DATA_EMPTY);
        // 执行修改
        UpdateWrapper<SystemJob> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda()
                .set(SystemJob::getDisabled, dto.getDisabled())
                .set(dto.getDisabled(), SystemJob::getNextFireTime, null)
                .eq(SystemJob::getId, dto.getId())
        ;
        systemJobService.update(updateWrapper);
        // 如果为禁用，则删除定时任务
        if (dto.getDisabled()) {
            try {
                schedulerProxy.deleteJob(job.getHandler());
            } catch (Exception e) {
                log.error("无法从调度中移除任务", e);
            }
        }
        // 如果未启用，则创建JOB并加入调度
        else {
            try {
                schedulerProxy.createAndScheduleCronJob(SchedulerProxy.CronJob.builder()
                        .id(job.getId())
                        .handler(job.getHandler())
                        .cronExpression(job.getCronExpression())
                        .disallowConcurrent(job.getDisallowConcurrent())
                        .build()
                );
            } catch (Exception e) {
                log.error("任务信息有误，无法将定时任务加入到调度！", e);
                throw new BusinessException(ResponseStatus.BAD_REQUEST, "任务信息有误，无法将定时任务加入到调度！");
            }
        }
    }

    /**
     * 分页查询
     *
     * @param pageWrap 分页参数
     * @return 分页数据
     */
    public PageData<SystemJobVO> findPage (PageWrap<QuerySystemJobDTO> pageWrap) {
        PageHelper.startPage(pageWrap.getPage(), pageWrap.getCapacity());
        List<SystemJobVO> result = systemJobMapper.search(pageWrap.getModel());
        return PageUtil.from(new PageInfo<>(result));
    }

    /**
     * 验证创建参数
     *
     * @param dto 创建参数
     */
    private void checkCreate (CreateSystemJobDTO dto) {
        AssertUtil.notEmpty(dto.getJobName(), "任务名称不能为空");
        AssertUtil.notEmpty(dto.getHandler(), "任务处理器不能为空");
        AssertUtil.notEmpty(dto.getCronExpression(), "CRON表达式不能为空");
        AssertUtil.notNull(dto.getDisallowConcurrent(), "是否禁用并发执行不能为空");
        AssertUtil.notNull(dto.getWithLog(), "是否记录日志不能为空");
    }
}
